package com.gitee.wsl.mathematics.geometry.d2.contour.segment

import com.gitee.wsl.mathematics.coordinate.d2.Coordinate2
import com.gitee.wsl.mathematics.geometry.api.CurveSegmentShape
import com.gitee.wsl.mathematics.geometry.d2.Shape2d
import com.gitee.wsl.mathematics.geometry.d2.contour.ShapeContour
import com.gitee.wsl.mathematics.geometry.d2.contour.sampleEquidistant
import com.gitee.wsl.mathematics.geometry.d2.contour.sampleEquidistantWithT
import com.gitee.wsl.mathematics.number.compareTo
import com.gitee.wsl.mathematics.vector.ext.distanceTo
import com.gitee.wsl.mathematics.vector.ext.minus
import com.gitee.wsl.mathematics.vector.ext.squaredDistanceTo
import com.gitee.wsl.mathematics.vector.vec2.Vector2

interface CurveSegmentShape2D<N:Number,V: Coordinate2<N, V>>: CurveSegmentShape<N, V>,Shape2d<N,V> {

    override val area get() = zero

    val simple: Boolean

    override val length: N
        get() = when (control.size) {
            0 -> (end - start).length
            1, 2 -> sumDifferences(adaptivePositions()) + zero
            else -> throw RuntimeException("unsupported number of control points")
        }

    override fun containsPoint(p: Coordinate2<N, *>): Boolean = nearestSegmentPoint(p).position == p

    fun nearestSegmentPoint(point: Coordinate2<N, *>): SegmentPoint

    override fun sub(t0: Double, t1: Double): CurveSegmentShape2D<N, V>{
        // ftp://ftp.fu-berlin.de/tex/CTAN/dviware/dvisvgm/src/Bezier.cpp
        var z0 = t0
        var z1 = t1

        if (t0 > t1) {
            z1 = t0
            z0 = t1
        }

        return when {
            z0 == 0.0 -> split(z1)[0]
            z1 == 1.0 -> split(z0).last()
            else -> split(z0).last().split(com.gitee.wsl.ext.number.map(z0, 1.0, 0.0, 1.0, z1))[0]
        }
    }

    override fun split(t: Double): Array<out CurveSegmentShape2D<N, V>>

    override val reverse: CurveSegmentShape2D<N, V>
    /**
     * Calculates the point at a given distance along this [Segment2D].
     * @param length the distance along the [Segment2D].
     * @param distanceTolerance the tolerance used for simplifying the [Segment2D], lower values
     * result in more accurate results, but slower calculation.
     *
     * @see [Segment2D.adaptivePositions]
     */
    fun pointAtLength(length: N, distanceTolerance: Double): V {
        when {
            length <= 0.0 -> return start
            length >= this.length -> return end
        }
        var remainingLength = length
        var currentPoint = start
        val points = adaptivePositions(distanceTolerance)
        for (point in points) {
            val segmentLength = currentPoint.distanceTo(point)
            if (remainingLength <= segmentLength) {
                val currentVector = point - currentPoint
                val tangent = currentVector / segmentLength
                return currentPoint + tangent * remainingLength
            }
            remainingLength -= segmentLength
            currentPoint = point
        }
        return end
    }

    fun derivative2(t: Float): Vector2<N, *>

    fun curvature(t: Float): Float

    /**
     * Recursively subdivides [Segment2D] to approximate Bézier curve.
     *
     * @param distanceTolerance The square of the maximal distance of each point from curve.
     */
    fun adaptivePositions(distanceTolerance: Double = 0.5): List<V> =
        adaptivePositionsWithT(distanceTolerance).map { it.first }


    fun adaptivePositionsWithT(distanceTolerance: Double = 0.5): List<Pair<V, Double>> = when (control.size) {
        0 -> listOf(start to 0.0, end to 1.0)
        1 -> BezierQuadraticSamplerT<N,V>().apply { this.distanceTolerance = distanceTolerance }
            .sample(start, control[0], end)

        2 -> BezierCubicSamplerT<N,V>().apply { this.distanceTolerance = distanceTolerance }
            .sample(start, control[0], control[1], end)

        else -> throw RuntimeException("unsupported number of control points")
    }

    /**
     * Samples specified amount of points on the [Segment2D].
     * @param pointCount The number of points to sample.
     */
    fun equidistantPositions(pointCount: Int, distanceTolerance: Double = 0.5): List<V> {
        return sampleEquidistant(adaptivePositions(distanceTolerance), pointCount)
    }

    fun equidistantPositionsWithT(pointCount: Int, distanceTolerance: Double = 0.5): List<Pair<V, Double>> {
        return sampleEquidistantWithT(adaptivePositionsWithT(distanceTolerance), pointCount)
    }


}

private fun<N:Number> sumDifferences(points: List<Coordinate2<N, *>>) =
    (0 until points.size - 1).sumOf { (points[it] - points[it + 1]).length.toDouble() }




